package com.niodata.dp.util;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;


public class JsonUtil {

  private static final ObjectMapper objMapper = new ObjectMapper();

  static {
    objMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    objMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
  }

  public static String toJson(Object obj) throws IOException {
    return objMapper.writeValueAsString(obj);
  }

  public static String toJson(Object obj, ObjectMapper mapper) throws IOException {
    return mapper.writeValueAsString(obj);
  }

  public static String toJson(Object obj, boolean prettyFormat) throws IOException {
    String json = objMapper.writeValueAsString(obj);
    if (prettyFormat) {
      Object jsonObj = objMapper.readValue(json, Object.class);
      json = objMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObj);
    }
    return json;
  }

  public static String toJson(Object obj, boolean prettyFormat, ObjectMapper mapper)
        throws IOException {
    String json = mapper.writeValueAsString(obj);
    if (prettyFormat) {
      Object jsonObj = mapper.readValue(json, Object.class);
      json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObj);
    }
    return json;
  }


  public static String formatPretty(String json) throws IOException {
    Object obj = objMapper.readValue(json, Object.class);
    return objMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
  }


  public static <K, V> Map<K, V> fromJsonMap(Class<K> keyClazz, Class<V> valueClazz, String json)
        throws IOException {
    return objMapper.readValue(json, objMapper.getTypeFactory()
          .constructMapType(HashMap.class, keyClazz, valueClazz));
  }


  public static <K, V> Map<K, V> fromJsonMap(Class<K> keyClazz, Class<V> valueClazz, String json,
        ObjectMapper mapper)
        throws IOException {
    return mapper.readValue(json, objMapper.getTypeFactory()
          .constructMapType(HashMap.class, keyClazz, valueClazz));
  }


  public static ObjectMapper newObjectMapper(String dateFormat) {
    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
          false);
    mapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES,
          false);
    mapper.setDateFormat(new SimpleDateFormat(dateFormat));
    return mapper;
  }


  public static <T> T fromJson(Class<T> clazz, String json)
        throws IOException {
    return objMapper.readValue(json, clazz);
  }

  public static <T> T fromJson(Class<T> clazz, String json, ObjectMapper mapper)
        throws IOException {
    return mapper.readValue(json, clazz);
  }

  public static <T> List<T> fromJsonList(Class<T> clazz, String json)
        throws IOException {
    return objMapper.readValue(json, objMapper.getTypeFactory()
          .constructCollectionType(List.class, clazz));
  }

  public static <T> List<T> fromJsonList(Class<T> clazz, String json, ObjectMapper mapper)
        throws IOException {
    return mapper.readValue(json, objMapper.getTypeFactory()
          .constructCollectionType(List.class, clazz));
  }

  public static <T> T fromJsonNode(Class<T> clazz, String json, String nodePath)
        throws IOException {
    JsonNode rootNode = objMapper.readTree(json);
    String[] nodes = nodePath.split("/");
    JsonNode finalNode = rootNode;
    for (String node : nodes) {
      finalNode = finalNode.get(node);
      if (finalNode == null) {
        throw new IOException("node path error");
      }
    }
    return objMapper.readValue(finalNode.asText(), clazz);
  }


  /**
   * parse json to flatten map,target node or json must be array.
   * @param json json string
   * @param nodePath json node path (eg . a/b/c/d)
   * @param flatArray array node
   * @return map list
   * @throws IOException ex
   */
  public static List<Map> parseJsonToFlattenMap(String json, String nodePath, boolean flatArray) throws IOException {
    String[] nodes = new String[0];
    if (nodePath != null && nodePath.length() > 0) {
      nodes = nodePath.split("/");
    }
    List<Map> list = null;
    if (json.startsWith("[")) {
      list = fromJsonList(Map.class, json);
    } else {
      Map map = fromJson(Map.class, json);
      if (nodes.length > 0) {
        for (int i = 0; i < nodes.length - 1; i++) {
          map = (Map) map.get(nodes[i]);
        }
        Object obj = map.get(nodes[nodes.length - 1]);
        if (List.class.isAssignableFrom(obj.getClass())) {
          list = (List) obj;
        } else {
          list = Lists.newArrayList((Map) obj);
        }
      } else {
        list = Lists.newArrayList(map);
      }
    }
    List<Map> res = new ArrayList();
    for (Map obj : list) {
      Map resMap = MapUtils.toFlattenMap(obj, flatArray);
      res.add(resMap);
    }
    return res;
  }


  /**
   * extract json to csv file.
   * @param json json string
   * @param csvFile csv file path
   * @param headerMap csv header map(header->json node path)
   * @param csvRootNode extract root json node,null/"" is json root
   */
  public static void extractToCsv(String json, String csvFile, Map<String, String> headerMap,
                                  String csvRootNode) throws IOException {
    List<Map> list = parseJsonToFlattenMap(json, csvRootNode, true);
    List<String> headers = new ArrayList<>(headerMap.keySet());
    Collections.sort(headers);
    List<String> keys = new ArrayList<>();
    for (String h : headers) {
      keys.add(headerMap.get(h));
    }
    BufferedWriter writer = new BufferedWriter(new FileWriter(csvFile, true));
    final CSVPrinter printer = CSVFormat.DEFAULT.builder().setHeader(headers.toArray(new String[headers.size()]))
        .setNullString("N/A").setIgnoreHeaderCase(false).build().print(writer);
    int batch = 0;
    for (Map row : list) {
      List<Object> columns = new ArrayList<>();
      for (String key : keys) {
        columns.add(row.get(key));
      }
      printer.printRecord(columns);
      batch++;
      if (batch == 1000) {
        printer.flush();
        batch = 0;
      }
    }
    printer.flush();
    printer.close();
    writer.close();
  }

}
